import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Normalizer {

	private StringBuilder normCode=null;
	private HashMap<Integer,String> identifierTable = null;
	private String origCode = null;
	private ScriptNode sn = null;

	public Normalizer(ScriptNode sn){
		this.sn = sn;
		this.origCode = sn.getOrigCode();
		this.identifierTable = new HashMap<Integer, String>();

	}
	
	private int FindNextVar(int start){
		int[] index = new int[5];
		Arrays.fill(index, -1);
		index[0] = this.normCode.indexOf(" var ",start);
		index[1] = this.normCode.indexOf("\rvar ",start);
		index[2] = this.normCode.indexOf("\nvar ",start);
		index[3] = this.normCode.indexOf("\tvar ",start);
		Arrays.sort(index);
		int i=0;
		while ((i<index.length) && (index[i]<0)) 
				i+=1;
		if (i>4)
			return(-1);
		else
			return(index[i]+1);
	}

	private Identifier FindIdentifier(int startIndex) throws DetectorException{
		int loc=0,j=0;
		//declarations types - var A=B; , var A; , var A , var A in B , var A=x,B=y,C=z
		int[] temp = new int[10];
		if (normCode.charAt(startIndex)!=',')
			startIndex=startIndex+4;
		else{
			startIndex+=1;
			while (normCode.charAt(startIndex)==' ')
				startIndex+=1;
		}
		//search identifier declaration type
		temp[0] = normCode.indexOf(";", startIndex);
		temp[1] = normCode.indexOf("=", startIndex);
		temp[2] = normCode.indexOf(" in ", startIndex);
		temp[3] = normCode.length();
		temp[4] = normCode.indexOf(",",startIndex);
		temp[5] = normCode.indexOf(" ",startIndex);
		//find the minimum value that is different from -1
		Arrays.sort(temp);
		while(temp[j]<=0){
			j=j+1;
		}
		if (j<=temp.length){
			loc=temp[j];
			Identifier iden = new Identifier();
			iden.setStartIndex(startIndex);
			iden.setEndIndex(loc);
			iden.setName(normCode.substring(startIndex, loc).trim());
			return (iden);
		}
		else 
			throw (new DetectorException("Problem with variables normalization. String = " + normCode));

	}

	private void RemoveComments(){

		int multiLineCommentStart = normCode.indexOf("/*"), multiLineCommentEnd=0;
		while(multiLineCommentStart>-1){
			multiLineCommentEnd = normCode.indexOf("*/",multiLineCommentStart);
			if (multiLineCommentEnd>-1) 
				normCode.replace(multiLineCommentStart, multiLineCommentEnd+2, "MULL_COMM");
			else{
				multiLineCommentStart = normCode.indexOf("/*",multiLineCommentStart+2);
				continue;
			}
			multiLineCommentEnd = normCode.lastIndexOf("MULL_COMM");
			multiLineCommentStart = normCode.indexOf("/*",multiLineCommentEnd);
		}

		int singleLineCommentStart = normCode.indexOf("//"), singleLineCommentEnd=0;
		while (singleLineCommentStart>-1){
			//ignore http://www or http%3a//
			if (singleLineCommentStart>0)
				if ((normCode.charAt((singleLineCommentStart-1))==':') || 
						(normCode.charAt(singleLineCommentStart-1)=='a')){
					//this means that this is not a remark but a url of the form http://...
					singleLineCommentStart = normCode.indexOf("//",singleLineCommentStart+1);
					continue;
				}
			//if (normCode.indexOf(str))
			singleLineCommentEnd = normCode.indexOf("\n", singleLineCommentStart);
			if (singleLineCommentEnd<0)
				singleLineCommentEnd=normCode.length();
			if (singleLineCommentStart>singleLineCommentEnd)
				System.out.println(singleLineCommentStart+","+singleLineCommentEnd);
			normCode.replace(singleLineCommentStart, singleLineCommentEnd, "SIN_COMM");
			singleLineCommentStart = normCode.indexOf("//",singleLineCommentEnd);
		}

	}

	public ScriptNode Normalize() throws DetectorException{

		String identifer = null;
		
		if (origCode.isEmpty()){
			sn.setNormCode(origCode);
			return(sn);
		}

		//remove strings of the form "<!-- generated -->" 
		//origCode = origCode.replaceAll("<!*>", "<!--CDATA-->");
	
		//replace special characters
		origCode = origCode.replaceAll("&amp;","&");			
		origCode = origCode.replaceAll("&lt;","<");			
		origCode = origCode.replaceAll("&gt;",">");			
		origCode = origCode.replaceAll("&#039;","'");		
		origCode = origCode.replaceAll("&quot;","\"");

		//replace \' and \" with white space to clearly identify strings
		origCode = origCode.replaceAll("\\\\'","");
		origCode = origCode.replaceAll("\\Q\\\"\\E","");

		//replace all strings inside double quotes
		origCode = origCode.replaceAll("[\"][^\"]*[\"]", "STR");
		//replace all strings inside single quotes
		origCode = origCode.replaceAll("['][^']*[']", "STR");
				
		//replace boolean values true and false with constant BOOL
		origCode = origCode.toLowerCase();
		origCode = origCode.replaceAll("\\Qtrue\\E", "BOOL");
		origCode = origCode.replaceAll("\\Qfalse\\E", "BOOL");
		
		// replace regEx with constant RegEx
		//origCode = origCode.replaceAll("/[^/].*/", "RegEx");


		normCode = new StringBuilder(origCode);
		
		Pattern p = Pattern.compile("<!.*>",Pattern.DOTALL);
		Matcher m = p.matcher(normCode);
		while (m.find()){
			//System.out.println("found at " + m.start()+ " end at " + m.end());
			normCode.replace(m.start(), m.end(), "<CDATA>");
			m = p.matcher(normCode);
		}

		//remove comments
		RemoveComments();

		/*replace identifiers with constant IDEN.
		identifiers will be found using original code because normalized code will
		change during run. */

		//find next index of variable declaration
		int i = normCode.indexOf("var ");
		boolean moreVars = false;
		int identEndIndex;
		while (i>-1){

			//find identifier name
			Identifier iden = FindIdentifier(i);
			identifer = iden.getName();
			//System.out.println(identifer);

			//add identifier to identifier table
			if (!identifierTable.containsKey(identifer.hashCode()))
				identifierTable.put(identifer.hashCode(), identifer);

			//check if identifier is an array declaration. If so, change identifier's value to const ARR
			//Here we check the declaration form myArray=[0,1,1..];
			//identEndIndex = normCode.indexOf(identifer,i+3) + identifer.length();
			identEndIndex = iden.getEndIndex();

			String curr=null;
			if (normCode.indexOf(";",identEndIndex)>-1)
				curr = normCode.substring(identEndIndex,normCode.indexOf(";",identEndIndex)).toLowerCase().trim();
			else{
				//System.out.println("Problematic identifiers value...skipping variable");
				sn.setSkip(true);
				i = FindNextVar(identEndIndex);
				continue;
			}

			String decType = "null";
			int in1 = 0,in2=0;

			if (curr.startsWith("=")){
				curr = curr.substring(1).trim();
				if (curr.startsWith("new")){
					//this declaration is a var a = new Array(1,2,3)
					in1 = curr.indexOf("new")+3;
					in2 = normCode.indexOf("new",identEndIndex) + 3;
					curr = curr.substring(in1).trim();
					if (curr.startsWith("array")){
						decType = "arrNew";
						in2 = normCode.indexOf("array",in2)+5;
						while (normCode.charAt(in2)==' ')
							in2 +=1;
						if (normCode.charAt(in2)!='('){
							//System.out.println("Problematic array declaration...skipping variable");
							sn.setSkip(true);
							i = FindNextVar(identEndIndex);
							continue;
						}
					}
				}
				else if (curr.charAt(0)=='['){
					//this declaration is a var a = [1,2,3]
					decType="arr";
					in2 = normCode.indexOf("[",identEndIndex);
				}
				else if (curr.charAt(0)=='{'){
					//this declaration is a var a = {a:b,c:d}
					decType="obj";
					in2 = normCode.indexOf("{",identEndIndex);
				}
			}else if (curr.startsWith(",")){
				decType = "moreVars";
				moreVars=true;
			}
			else if (curr.startsWith("/")){
				decType = "regEx";
				in2 = normCode.indexOf("/",identEndIndex);
			}


			//check if identifier is an array declaration.of the form var f=[1,2,3]. If so, change identifier's value to const OBJ
			if (decType.equals("arr")){
				int cnt=1,end=in2;
				while (cnt!=0){
					end+=1;
					if (end>normCode.length() || normCode.charAt(end)==';'){
						//System.out.println("Problematic array declaration...skipping ");
						sn.setSkip(true);
						break;
					}
					if (normCode.charAt(end)==']')
						cnt-=1;
					else if (normCode.charAt(end)=='[')
						cnt+=1;
				}
				normCode.replace(in2+1, end, "ARR");
				i = FindNextVar(in2);
			}//check if identifier is an object declaration. If so, change identifier's value to const OBJ
			else if (decType.equals("obj")){
				int cnt=1,end=in2;
				while (cnt!=0){
					end+=1;
					if (end>normCode.length() || normCode.charAt(end)==';'){
						//System.out.println("Problematic object declaration...skipping: ");
						sn.setSkip(true);
						break;
					}
					if (normCode.charAt(end)=='}')
						cnt-=1;
					else if (normCode.charAt(end)=='{')
						cnt+=1;
				}
				normCode.replace(in2+1, end, "OBJ");
				i = FindNextVar(in2);
			}//check if identifier is an Array Declaration of the form var f=new Array(45)
			else if (decType.equals("arrNew")){
				int cnt=1,end =in2;
				while (cnt!=0){
					end+=1;
					if (end>normCode.length() || normCode.charAt(end)==';'){
						//System.out.println("Problematic array declaration...skipping: ");
						sn.setSkip(true);
						break;
					}
					if (normCode.charAt(end)=='(')
						cnt+=1;
					else if (normCode.charAt(end)==')')
						cnt-=1;
				}
				normCode.replace(in2+1, end, "ARR");
				i = FindNextVar(in2);
			}
			else if (decType.equals("regEx")){
				int end = normCode.indexOf("/",in2);
				normCode.replace(in2, end, "REGEX");
				i = FindNextVar(in2);

			}
			else if (decType.equals("moreVars"))
				i = normCode.indexOf(",",identEndIndex);
			else
				i = FindNextVar(i+4);
		}
		String output = normCode.toString();

		//replace every identifier and all its appearnces in the code
		Collection<String> c = identifierTable.values();
		Iterator<String> iter = c.iterator();
		while (iter.hasNext()){
			identifer=(String)iter.next();
			output = output.replaceAll("[^A-Za-z0-9\"\']"+identifer + "[^A-Za-z0-9\"\']", " IDEN");
			/*else
				System.out.println("Couldn't find a match for the next variable \r\n" + identifer);*/
		}
		//replace all integers and floats with constant NUM
		output = output.replaceAll("[+-]?\\d+[\\.]{0,1}\\d*", " NUM");
		sn.setNormCode(output);
		return(sn);

	}

}
